﻿using System;
using NetMQ.Sockets;
using NetMQ;
using Bit.Art.Distributed;
using Bit.Art.Core.Extensions;
using Bit.Art.Core.Models;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using System.IO;
using Bit.Art.Db;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;

namespace Bit.Art.Bookeeper
{
    public class Program
    {
        internal static IConfigurationRoot Configuration { get; set; }
        public static IServiceProvider GlobalServiceProvider { get; set; }
        private static bool s_Running = true;
        private static long _orderNo;
#if UNITTEST
        public static System.Threading.AutoResetEvent s_autoResetEventForTest { get; private set; } = new System.Threading.AutoResetEvent(false);
        public static System.Threading.AutoResetEvent s_filledAutoEventForTest { get; private set; } = new System.Threading.AutoResetEvent(false);
#endif
        // 最新的最优买单和卖单，0是卖单，1是买单
        private static Order[] s_LatestOrders = new Order[2];

        public static void Main(string[] args)
        {
            Log.Logger.Information("====== BOOKEEPER ======");
            InitializeConfiguration();

            var services = new ServiceCollection();
            services.AddDbContext<ExchangeDb>(options =>
                options.UseMySql(Configuration.GetConnectionString("ExchangeDb")), ServiceLifetime.Transient);
            GlobalServiceProvider = services
                .AddLogging()
                .BuildServiceProvider();

            var rediscnn = Configuration["redis"];
            var memcache = Memcache.Create(rediscnn);
            var task = StartEngine(memcache);
            Task.WaitAll(task);
        }

        private static void InitializeConfiguration()
        {
            var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
            var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .AddJsonFile($"appsettings.{environment}.json");
            Configuration = builder.Build();
            ApplicationLogging.LoggerFactory.AddConsole(Configuration.GetSection("Logging"));
        }

        public static Task StartEngine(Memcache memcache)
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
                .MinimumLevel.Override("System", LogEventLevel.Warning)
                .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information)
                .Enrich.FromLogContext()
                .WriteTo.File("bookeeper.log", rollingInterval: RollingInterval.Day)
                .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate)
                .CreateLogger();
            
            var task = Task.Run(async () =>
            {
                s_LatestOrders = new Order[2] { new Order() { Side = (int)TradeSide.Buy }, new Order() { Side = (int)TradeSide.Sell } };

                try
                {
                    // TODO: 需要将硬编码的load balance端口放到配置文件里
                    using (var receiver = new PullSocket(string.Format("@{0}", SiteConfiguration.MatchResultSinkEndpoint)))
                    {
                        using (var publisher = new PublisherSocket(SiteConfiguration.PublishEndpoint))
                        {
                            publisher.Options.SendHighWatermark = SiteConfiguration.PublisherSendHighWatermark;

#if UNITTEST
                            s_autoResetEventForTest.Set();
#endif
                            while (s_Running)
                            {
                                try
                                {
                                    Log.Logger.Debug($"[BOOKEEPER] ready to read message from ZeroMq");
                                    var msg = receiver.ReceiveMultipartMessage();
                                    var bytes = msg.Last.Buffer;
                                    if (bytes.Length == 1) continue;

                                    var order = Order.FromBytes(bytes);
                                    System.Threading.Interlocked.Increment(ref _orderNo);
                                    Log.Logger.Debug(string.Format($"[BOOKEEPER] No {_orderNo}, 接收到{order.Id}，价格：{order.Price}，数量：{order.Volume}，剩余数量：{order.VolumeRemaining}，方向：{order.Side}！"));

                                    // 只要有成交，就需要广播最新价;
                                    if (order.VolumeRemaining != order.Volume)
                                    {
                                        // 1.1. 如果完全成交，更新用户的内存缓存余额，并更新数据库中的用户余额
                                        await UpdateUserBalance(order, memcache);
                                        await PublishFilled(publisher, order, memcache);
                                    }

                                    // 只要有挂单，就需要广播新订单
                                    if (order.VolumeRemaining != 0 && !order.IsCancelled)
                                    {
                                        if (order.Side == (int)TradeSide.Sell)
                                        {
                                            s_LatestOrders[0].Price = order.Price;
                                            s_LatestOrders[0].VolumeRemaining = order.VolumeRemaining;
                                        }
                                        else
                                        {
                                            s_LatestOrders[1].Price = order.Price;
                                            s_LatestOrders[1].VolumeRemaining = order.VolumeRemaining;
                                        }

                                        // 1.1. 有挂单需要更新用户的缓存和数据库的余额信息
                                        // await UpdateUserBalance(order, memcache);
                                        await PublishOpened(publisher, order, memcache);
                                    }

                                    // 要取消的订单
                                    if (order.IsCancelled)
                                    {
                                        await RemoveOpenOrderFromCache(order, memcache);
                                        await ReverseUserBalance(order, memcache);
                                        await CancelDbOrder(order);
                                    }
#if UNITTEST
                                    s_autoResetEventForTest.Set();
#endif
                                }
                                catch (Exception e)
                                {
#if UNITTEST
                                    s_autoResetEventForTest.Set();
#endif
                                    Log.Logger.Error(e, "接收匹配结果碰到严重错误");
                                }
                            }
                        }
                    }
                }
                catch (Exception e)
                {
#if UNITTEST
                    s_autoResetEventForTest.Set();
#endif
                    Log.Logger.Error(e, "无法启动bookeeper");
                }
            });

            return task;
        }

        private static async Task RemoveOpenOrderFromCache(Order order, Memcache memcache)
        {
            // 删除缓存的公开订单
            await memcache.RemoveOpenOrder(order.UserId, order.Id);
            // 还要删除缓存的索引
            await memcache.RemoveUserOrderIndex(order);
        }

        private static async Task PublishFilled(PublisherSocket publisher, Order order, Memcache cache)
        {
            if (order.OrderType == (int)OrderType.Limit)
            {
                // 1.2. 删除该订单对应的挂单
                await RemoveOpenOrderFromCache(order, cache);
                // 1.3. 需要同时更新数据库里的订单情况
                await UpdateDbOrder(order);
                if (order.Taker)
                {
                    var tick = new Tick()
                    {
                        Market = order.Market,
                        Price = order.Price.Value,
                        Volume = order.Volume,
                        Side = order.Side,
                        Timestamp = Utility.GetUnixTimestamp()
                    };

                    if (s_LatestOrders[0].Price.HasValue) tick.LatestAsk = s_LatestOrders[0].Price.Value;
                    if (s_LatestOrders[1].Price.HasValue) tick.LatestBid = s_LatestOrders[1].Price.Value;
                    publisher.SendMoreFrame(SiteConfiguration.PUBLISH_TOPIC_TICK).SendFrame(tick.ToJson());
                }
            }

#if UNITTEST
            if (order.VolumeRemaining == 0)
            {
                Log.Logger.Debug(string.Format($"[BOOKEEPER] No {_orderNo}, {order.Id}，价格：{order.Price}，数量：{order.Volume}，剩余数量：{order.VolumeRemaining}，方向：{order.Side} set filled event!"));
                s_filledAutoEventForTest.Set();
            }
#endif
        }

        private static async Task PublishOpened(PublisherSocket publisher, Order order, Memcache cache)
        {
            // 1.2. 如果没有全部成交，将订单加入到挂单缓存中，并定期更新数据库中的订单成交情况
            await AddOpenOrder(order, cache);
            // TODO: 添加测试市价单没有完全成交的自动化测试
            // 例如目前没有任何订单，测试下达市价单是什么情况
            // 基本上市价单不可能存在挂单的情况
            publisher.SendMoreFrame(SiteConfiguration.PUBLISH_TOPIC_ORDER).SendFrame(order.ToJson());
        }
        
        private async static Task<bool> AddOpenOrder(Order order, Memcache cache)
        {
            var result = await cache.AddOpenOrder(order);
            result = result && await UpdateDbOrder(order) == 1;
            return result;
        }

        private async static Task<int> CancelDbOrder(Order order)
        {
            using (var db = GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var affected = await db.Database.ExecuteSqlCommandAsync("UPDATE Orders SET IsCancelled = 1 WHERE Id = @p0", order.Id);
                return affected;
            }
        }

        private async static Task<int> UpdateDbOrder(Order order)
        {
            using (var db = GlobalServiceProvider.GetRequiredService<ExchangeDb>())
            {
                var affected = 0;
                if (!order.IsCancelled)
                {
                    affected = await db.Database.ExecuteSqlCommandAsync("UPDATE Orders SET VolumeRemaining = @p0, QuoteVolumeDealed = @p1, Taker = @p2 WHERE Id = @p3",
                        order.VolumeRemaining, order.QuoteVolumeDealed, order.Taker, order.Id);
                }
                return affected;
            }
        }

        private async static Task<bool> UpdateUserBalance(Order order, Memcache cache)
        {
            if (!Utility.SplitMarket(order.Market, out string quote, out string coin)) return false;
            
            // 举例：BTC/USDT，quote: USDT, coin: BTC
            var bid = order.Side == (uint)TradeSide.Buy;
            var currency = bid ? quote : coin;
            // 本次撮合成功的quote，即USDT
            var dealedQuote = order.CurrentDealedQuote;
            // TODO: 需要严格确保CurrentDealed不为0！
            // 本次撮合成功的coin，即BTC
            var dealedCoin = order.CurrentDealed;
            var releasedPendingQuote = 0m;
            // 计算手续费，手续费计算通过VipLevel，默认手续费是千一，但等级高的客户可能是万五，甚至是万一，viplevel默认值是1000，因此需要除以一百万
            if (bid && order.OrderType == (int)OrderType.Limit)
                releasedPendingQuote = dealedCoin * order.Price.Value * (1 + order.GetFeeRate());

            // 计算手续费
            if (bid)
            {
                dealedQuote = dealedQuote * (1 + order.GetFeeRate());
            }
            else
            {
                dealedQuote = dealedQuote * (1 - order.GetFeeRate());
            }

            Log.Logger.Information($"【Bookeeper】更新余额，side: {order.Side}, price: {order.Price}, volume: {order.Volume}, volumeRemaining: {order.VolumeRemaining}, currentDealed: {order.CurrentDealed}, currentDealedQuote: {order.CurrentDealedQuote}, quoteVolume: {order.QuoteVolume}, quoteVolumeDealed: {order.QuoteVolumeDealed}");

            var result = await Task.Run(() => {
                lock (cache)
                {
                    var ret = cache.UpdateOrAddBalance(order, bid, quote, coin, dealedQuote, dealedCoin, releasedPendingQuote).Result;
                    return ret;
                }
            });
            
            // 更新数据库里的余额
            var affected = await UpdateBalance(order, bid, quote, coin, dealedQuote, dealedCoin, releasedPendingQuote);
            return result && affected == 1;
        }

        private static async Task<bool> ReverseUserBalance(Order order, Memcache cache)
        {
            if (!Utility.SplitMarket(order.Market, out string quote, out string coin)) return false;

            var bid = order.Side == (uint)TradeSide.Buy;
            var currency = bid ? quote : coin;
            var reversed = bid ? order.LockedBalance - (order.QuoteVolumeDealed * (1 + order.GetFeeRate())) : order.VolumeRemaining;
            var result = await cache.ReverseBalance(order, currency, reversed);
            var affected = await ReverseDbBalance(order, currency, reversed);
            return result && affected == 1;
        }

        private async static Task<int> ReverseDbBalance(Order order, string coin, decimal released)
        {
            using (var db = GlobalServiceProvider.GetService<ExchangeDb>())
            {
                // TODO: 需要检查买家和卖家余额增减情况
                // 因为订单是分开广播的，需要在撮合的时候，将撮合成功的每笔订单都广播出来
                int affected = 0;
                affected = await db.Database.ExecuteSqlCommandAsync(
                    "UPDATE AccountBalances SET Pending = Pending - @p0 WHERE AccountId = @p1 AND Currency = @p2",
                    released, order.UserId, coin);
                return affected;
            }
        }

        private async static Task<int> UpdateBalance(Order order, bool bid, string quote, string coin, 
            decimal dealedQuote, decimal dealedCoin, decimal releasedQuote)
        {
            // 打开数据链接
            using (var db = GlobalServiceProvider.GetService<ExchangeDb>())
            {
                // TODO: 需要检查买家和卖家余额增减情况
                // 因为订单是分开广播的，需要在撮合的时候，将撮合成功的每笔订单都广播出来
                int affected = 0;
                // 限价买单
                if (order.OrderType == (int)OrderType.Limit && bid)
                {
                    // 第一条语句，更新买家的quote，即USDT，第二条语句，更新买家的coin，即BTC
                    affected = await db.Database.ExecuteSqlCommandAsync(
                        "UPDATE AccountBalances SET Balance = Balance - @p0, Pending = Pending - @p5 WHERE AccountId = @p1 AND Currency = @p2;" +
                        "UPDATE AccountBalances SET Balance = Balance + @p3 WHERE AccountId = @p1 AND Currency = @p4",
                        dealedQuote, order.UserId, quote, dealedCoin, coin, releasedQuote);
                }
                // 限价卖单
                else if (order.OrderType == (int)OrderType.Limit && !bid)
                {
                    // 第一条语句，更新卖家的quote，即USDT，第二条语句，更新卖家的coin，即BTC
                    affected = await db.Database.ExecuteSqlCommandAsync(
                        "UPDATE AccountBalances SET Balance = Balance + @p0 WHERE AccountId = @p1 AND Currency = @p2;" +
                        "UPDATE AccountBalances SET Balance = Balance - @p3, Pending = Pending - @p3 WHERE AccountId = @p1 AND Currency = @p4",
                        dealedQuote, order.UserId, quote, dealedCoin, coin);

                }
                return affected;
            }
        }
    }
}
